Rust Web 错误处理与中间件设计
1. 这是什么
当你已经会写 Axum 或 Actix Web 的基础路由后,下一步很快就会遇到两个工程问题:
- 错误怎么统一处理
- 横切逻辑应该放在哪里
这篇讲的就是这两件事:
- 错误处理:请求失败后,应该怎样稳定、清晰地对外表达
- 中间件设计:日志、鉴权、请求追踪、限流、上下文注入这类横切逻辑,应该怎样组织
一句话理解:
- handler 负责处理当前请求的业务入口
- 错误处理负责定义失败语义
- 中间件负责承载跨多个路由共享的横切规则
2. 为什么这两个主题总会一起出现
因为在真实 Web 服务里,它们都属于“边界层治理”。
当请求进入系统时,你要决定:
- 路由怎么分发
- 参数怎么提取
- 业务怎么调用
- 错误怎么映射
- 日志和 tracing 怎么加
- 鉴权、限流、超时、上下文信息在哪里插入
这说明错误处理和中间件都不是附属细节,
而是整个请求处理链路的结构问题。
3. 先建立直觉
3.1 错误处理解决的是“失败如何被表达”
失败不是只有一种。
在 Web 服务里,你会碰到:
- 参数不合法
- 资源不存在
- 权限不足
- 数据库失败
- 下游服务超时
- 内部未知错误
真正的问题不是“会不会失败”,而是:
- 这些失败对外应该长什么样
- 哪些要暴露成 4xx
- 哪些要暴露成 5xx
- 哪些细节只该写日志,不该直接返回给客户端
3.2 中间件解决的是“重复横切逻辑放哪”
很多逻辑不是某个 handler 独有,而是很多接口都需要:
- 请求日志
- request id
- tracing span
- 鉴权
- CORS
- 超时
- 压缩
- 限流
如果这些都写进每个 handler,代码会很快失控。
所以中间件的意义就是:
- 把这些横切能力从业务 handler 里抽出来,放到统一链路层处理
4. Rust Web 错误处理最核心的工程直觉
4.1 错误类型其实是在定义 HTTP 边界语义
在普通 Rust 模块里,错误类型定义模块边界。
在 Web 场景里,这件事会进一步表现成:
- 某类错误最终映射成什么 HTTP 语义
- 客户端能否稳定理解这个失败
- 内部错误细节是否被妥善隐藏
所以 Web 错误处理不能只停留在“把 Err 传上去”,而要继续问:
- 这个
Err最后准备以什么响应形式出现
4.2 对外错误语义和内部诊断信息要分层
一个成熟服务里,错误至少有两层:
- 对外层:返回给客户端的状态码、错误码、消息
- 对内层:日志、source chain、上下文、堆栈、调用链信息
这两层不应该混在一起。
也就是说:
- 客户端看到的错误应该稳定、清晰、可预期
- 开发者看到的内部信息应该足够详细、可排查
4.3 统一错误出口很重要
如果每个 handler 都各自决定怎么拼错误响应,很快就会出现:
- 格式不统一
- 状态码风格混乱
- 前端处理成本上升
- 日志和错误映射重复
所以真正成熟的做法往往是:
- 让不同类型的错误在边界层统一收敛
- 用统一响应结构输出给客户端
这样项目的错误语义才会稳定。
5. Rust Web 中间件设计最核心的工程直觉
5.1 中间件不是“所有公共逻辑的垃圾桶”
虽然中间件承载横切逻辑,但不代表凡是公共代码都该塞进去。
更适合放进中间件的,通常是:
- 与请求链路强相关
- 对多个路由统一生效
- 不属于某个具体业务领域
比如:
- 日志
- request id
- tracing span
- 鉴权前置检查
- 限流
- 通用 header 处理
而不是:
- 某个具体业务流程的判断分支
- 领域规则本身
5.2 中间件本质上是在控制“请求经过系统时被怎样包裹”
你可以把中间件想成一层层包裹请求处理链路的壳。
请求进入系统时:
- 可能先经过日志记录
- 再经过鉴权
- 再经过 tracing 注入
- 再进入 handler
- 出错后再被统一映射成响应
所以中间件不是“附加组件”,而是请求生命周期的一部分。
5.3 顺序非常重要
中间件的执行顺序经常会直接影响行为:
- 先打日志还是先鉴权
- request id 在哪一层注入
- timeout 应该包裹哪些逻辑
- 错误映射发生在多靠外的一层
所以中间件设计不是“有没有”,而是“链路顺序是否合理”。
6. 错误处理和中间件为什么要一起设计
因为它们常常互相配合:
- 中间件可以注入 request id,错误响应里可能需要带上它
- tracing 中间件会记录错误上下文
- 统一错误出口可能本身就依赖某层公共处理逻辑
- 鉴权中间件可能提前拦截并产出标准化错误响应
也就是说,错误处理和中间件都在塑造同一件事:
- 请求进入系统后,成功和失败分别如何被组织
7. Axum / Actix 里真正该优先建立的能力
不管你站在哪个框架上,先抓住这些能力最重要:
- 统一错误响应结构
- 清晰区分 4xx 和 5xx
- 内部诊断信息不裸露给外部
- 日志 / tracing 放进统一链路
- 鉴权、限流、request id 这类横切逻辑从 handler 中抽离
- 保持 handler 薄、边界清晰
只要这几件事做对,框架 API 细节差异反而没那么关键。
8. 常见误区
8.1 误区一:错误处理就是返回状态码
不够。
状态码只是外层结果,真正重要的是错误语义是否稳定、统一、可维护。
8.2 误区二:所有公共逻辑都应该做成中间件
不对。
只有真正的横切请求链路逻辑,才适合放进中间件。
8.3 误区三:只要能把错误打到日志里,外部响应随便点也没关系
不行。
客户端也需要稳定、可理解的失败语义。
8.4 误区四:中间件越多越专业
并不是。
层次过多、顺序混乱、职责不清,反而会让链路难以理解和调试。
9. 一个更实用的判断思路
当你在设计 Rust Web 的错误处理和中间件时,可以先问:
- 这个错误是给客户端看,还是给开发者排查看
- 这个逻辑是某个 handler 独有,还是全链路共享
- 这层逻辑应该放在 handler、服务层,还是中间件层
- 这个中间件的位置会不会影响日志、鉴权、超时或错误映射顺序
- 当前设计是否让 handler 更薄、更清晰,而不是更臃肿
10. 建议学习顺序
建议按这个顺序继续深入:
- 先建立统一错误响应结构
- 再梳理 4xx / 5xx 的分类方式
- 再加 request id、tracing、访问日志
- 再抽离鉴权、限流、超时等中间件
- 最后统一整理错误出口、中间件顺序和项目模块结构
11. 自测标准
- 能解释 Web 错误处理为什么不只是“返回状态码”
- 能知道对外错误语义与内部诊断信息应该分层
- 能理解中间件的本质是承载横切请求链路逻辑
- 能意识到中间件顺序会直接影响系统行为
- 能判断某段逻辑该放在 handler、服务层还是中间件层